package cache

import (
	"context"
	"golang/cache/util"
	"time"
)

type LRUCache struct {
	shards    []*cacheShard
	hasher    util.Hasher
	shardMask uint64 // mask
	opts      *Options
}

func NewCache(shards int, options ...Option) (Cache, error) {
	if !util.IsPowerOfTwo(shards) {
		return nil, ErrInvalidShardLength
	}
	opts := loadOptions(options...)
	opts.ShardLen = shards
	repair(opts)
	cache := &LRUCache{
		shards:    make([]*cacheShard, opts.ShardLen),
		shardMask: uint64(shards - 1),
		opts:      opts,
	}

	for index := range cache.shards {
		cache.shards[index] = newCacheShard(cache, &cacheShardOptions{
			MaxCount:       opts.MaxCount,
			ExpireInterval: opts.ExpireInterval,
		})
	}
	return cache, nil
}

func (c *LRUCache) Foreach(fn func(key string, item *Item)) {
	for _, shard := range c.shards {
		shard.foreach(fn)
	}
}
func (c *LRUCache) ForeachExpired(fn func(key string, item *Item)) {
	for _, shard := range c.shards {
		shard.foreachExpired(fn)
	}
}

func (c *LRUCache) Get(key string) (interface{}, error) {
	hashedKey := c.hasher.Sum64(key)
	shard := c.getShard(hashedKey)
	return shard.get(key)
}

func (c *LRUCache) Set(key string, value interface{}, duration time.Duration) error {
	hashedKey := c.hasher.Sum64(key)
	shard := c.getShard(hashedKey)
	return shard.set(key, value, duration)
}

func (c *LRUCache) getShard(hashKey uint64) *cacheShard {
	return c.shards[hashKey&c.shardMask]
}

func (c *LRUCache) Delete(key string) error {
	hashedKey := c.hasher.Sum64(key)
	shard := c.getShard(hashedKey)
	return shard.delete(key, Deleted)
}

func (c *LRUCache) Clear(ctx context.Context) error {

	for _, shard := range c.shards {
		_ = shard.clear(ctx)
	}

	return nil
}
